﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Demo.Framework.PostProcessing
{
    internal class GaussianBlur : PostProcessor
    {
        private readonly float _blurAmount;

        private readonly float[] _weightsH;
        private readonly float[] _weightsV;
        private readonly Vector2[] _offsetsH;
        private readonly Vector2[] _offsetsV;

        private readonly RenderCapture _capture;

        public RenderCapture ResultCapture = null;

        public GaussianBlur(GraphicsDevice graphicsDevice, ContentManager content,
                            float blurAmount)
            : base(content.Load<Effect>("Effects/GaussianBlur"), graphicsDevice)
        {
            this._blurAmount = blurAmount;

            // Calculate weights/offsets for horizontal pass
            CalcSettings(1.0f/graphicsDevice.Viewport.Width, 0,
                         out _weightsH, out _offsetsH);

            // Calculate weights/offsets for vertical pass
            CalcSettings(0, 1.0f/graphicsDevice.Viewport.Height,
                         out _weightsV, out _offsetsV);

            _capture = new RenderCapture(graphicsDevice);
        }

        private void CalcSettings(float w, float h, out float[] weights, out Vector2[] offsets)
        {
            // 15 Samples
            weights = new float[15];
            offsets = new Vector2[15];

            // Calulate values for center pixel
            weights[0] = GaussianFn(0);
            offsets[0] = new Vector2(0, 0);

            float total = weights[0];

            // Calculate samples in pairs
            for (int i = 0; i < 7; i++)
            {
                // Weight each pair of samples according to Gaussian function
                float weight = GaussianFn(i + 1);
                weights[i*2 + 1] = weight;
                weights[i*2 + 2] = weight;
                total += weight*2;

                // Samples are offset by 1.5 pixels, to make use of
                // filtering halfway between pixels
                float offset = i*2 + 1.5f;
                Vector2 offsetVec = new Vector2(w, h)*offset;
                offsets[i*2 + 1] = offsetVec;
                offsets[i*2 + 2] = -offsetVec;
            }

            // Divide all weights by total so they will add up to 1
            for (int i = 0; i < weights.Length; i++)
                weights[i] /= total;
        }

        private float GaussianFn(float x)
        {
            return (float) ((1.0f/Math.Sqrt(2*Math.PI*_blurAmount*_blurAmount))*
                            Math.Exp(-(x*x)/(2*_blurAmount*_blurAmount)));
        }

        public override void Draw()
        {
            // Set values for horizontal pass
            Effect.Parameters["Offsets"].SetValue(_offsetsH);
            Effect.Parameters["Weights"].SetValue(_weightsH);

            // Render this pass into the RenderCapture
            _capture.Begin();
            base.Draw();
            _capture.End();

            // Get the results of the first pass
            Input = _capture.GetTexture();

            if (ResultCapture != null)
                ResultCapture.Begin();

            // Set values for the vertical pass
            Effect.Parameters["Offsets"].SetValue(_offsetsV);
            Effect.Parameters["Weights"].SetValue(_weightsV);

            // Render the final pass
            base.Draw();

            if (ResultCapture != null)
                ResultCapture.End();
        }
    }
}
